package util;

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import beans.Color;
import beans.Move;
import board.Board;
import board.Game;



/**
 * This class can be used to read PGN files, and to get from each game of the file a representation of the game in a "Game" class,
 * which will only include the moves of the principal line of the game, ignoring comments and alternative lines. 
 * 
 * @author Josep M Sobrepere
 *
 */
public class PGNBasicReader {
	
	/**
	 * This is the main function of the class and can be used to read the necessary information from all the games 
	 * contained in the file.
	 * 
	 * @param filePath path of the file to be read
	 * @return a list of the games in the file
	 */
	public static List<Game> read(String filePath){		
		StringBuilder sb = new StringBuilder();
		LinkedList<Game> games= new LinkedList<Game>();
		try{
			  FileInputStream fstream = new FileInputStream(filePath);			
			  DataInputStream in = new DataInputStream(fstream);
			  BufferedReader br = new BufferedReader(new InputStreamReader(in));
			  String strLine;
			  //boolean printIt=false;
			  while ((strLine = br.readLine()) != null)   {
				  sb.append(strLine).append("\r\n");
			  }
			  in.close();
			  			  
			  List<String> rawGames = splitPGN(sb.toString());
			  int i=0;
			  for (String rawGame: rawGames){
				  i++;				  
				  games.add(parseGame(rawGame/*,true*/));
				  //printIt=false;
			  }			  
			  
		}catch (IOException e){
			  System.err.println("Error: " + e.getMessage());
		}
		
		return games;		
	}
	
	/**
	 * This class can be used to parse a raw game represented in short algebraic notation into a 
	 * Game class.
	 * 
	 * @param game an String with the raw game represented in short algebraic notation.
	 * @return a Game class with the relevant information of the game	 
	 */
	public static Game parseGame(String game/*, boolean printIt*/) {		
		HashMap <String, String> gameProperties = new HashMap<String, String>();		
		BufferedReader br = new BufferedReader(new StringReader(game));
		String line;
		StringBuilder buffer = new StringBuilder();
		

		try {
			while ((line = br.readLine()) != null) {
				line = line.trim();
				if (line.startsWith("[")) {
					try {
						String tagName = line.substring(1, line.indexOf(" "));
						String tagValue = line.substring(line.indexOf("\"") + 1,
								line.lastIndexOf("\""));
						gameProperties.put(tagName, tagValue);					
					} catch (IndexOutOfBoundsException e) {

					}
				} else {
					if (!line.isEmpty()) {
						buffer.append(line + " ");
					}
				}
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		String regex = "\\{.*?\\}";
	    Pattern pattern = Pattern.compile(regex);
	    Matcher matcher = pattern.matcher(buffer);  
	    String tmp = matcher.replaceAll("");
	    
	    tmp = takeOutNestedTokens(tmp, '(', ')');
	    
	   
	    while(tmp.contains("  ")){
	    	tmp=tmp.replace("  ", " ");
	    }	        
	    	    	    
	    regex = "\\s*\\d+\\.+\\s*";
	    pattern = Pattern.compile(regex);
	    matcher = pattern.matcher(tmp);
	    tmp = matcher.replaceAll(" ");	    
	    
	    
	 	    
	    regex = "\\s*\\$+\\d+\\s*";
	    pattern = Pattern.compile(regex);
	    matcher = pattern.matcher(tmp);
	    tmp = matcher.replaceAll(" ");
	    
	   
	    tmp = tmp.replace("0-1", "").replace("1-0", "").replace("1/2-1/2", "").replace("*", "").trim();
	    
	    	    
	    Board board = new Board();	    
	    LinkedList<Move> moves = new LinkedList<Move>();		
		String[] movesShortNotation = tmp.split(" ");
		
		int i=0;
		for(String moveSN : movesShortNotation){
			Color turn = ((i%2)==0)?Color.WHITE:Color.BLACK;			
			Move move = MoveParser.fromShortNotation(moveSN, turn, board, (i/2)+1);
			board.registerMove(move);
			/*if(printIt){
				System.out.println("Move " + ((i/2)+1) + " " + turn );
				board.printIt(true);
				System.out.println(move.toString());
				System.out.println();
			}	*/
			if(moves.size()>0){
				Move lastMove = moves.getLast();
				move.setPreviousMove(lastMove);				
				lastMove.setNextMove(move);
			}						
			
			moves.add(move);
			i++;
		}
		
		return new Game(gameProperties,moves,board,game);

	}
	
	/**
	 * There's no possible way to take out the content in between nested nodes using REGEX, therefore this
	 * function can be used to take out the content in between the indicated nested nodes.
	 *  
	 * @param input the string with the original content.
	 * @param open the mark for opening a nested node
	 * @param close the mark for closing a nested node
	 * @return the original input without the content in between the specified nested nodes,
	 */
	private static String takeOutNestedTokens(String input, char open, char close){
		StringReader sr = new StringReader(input);		
		StringWriter sw = new StringWriter();		
		int openTokens=0;		
		char c[] = {' '};
		try{
			while (sr.read(c)!=-1){
				if(c[0]==open){
					openTokens++;
				}
				if(c[0]==close){
					openTokens--;
				}
				if(openTokens==0 && c[0]!=close){
					sw.append(c[0]);
				}				
			}
		}catch(Exception e){
			
		}
		return sw.toString();
	}
	
	/**
	 * Function used to get the different raw games in the PGN file.
	 * 
	 * @param pgn string with all the games
	 * @return a list with the games splited
	 */
	private static List<String> splitPGN(String pgn) {
		List<String> pgnGames = new LinkedList<String>();
		BufferedReader br = new BufferedReader(new StringReader(pgn));
		String line;
		StringBuilder buffer = new StringBuilder();
		
		try {
			while ((line = br.readLine()) != null) {
				line = line.trim();
				
				if (!line.isEmpty()) {
					buffer.append(line + "\r\n");
					
					if (line.endsWith("1-0") || line.endsWith("0-1") || line.endsWith("1/2-1/2") || line.endsWith("*")) {
						pgnGames.add(buffer.toString());
						buffer.delete(0, buffer.length());
					}
				}			
			}
			br.close();	
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}				
		return pgnGames;
	}
}
